package com.abel.repository.impl;

import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Restrictions;
import org.hibernate.exception.ConstraintViolationException;

import com.abel.datamodel.type.UserRoles;
import com.abel.datamodel.type.Users;
import com.abel.domain.AccountInfo;
import com.abel.domain.AuthPrincipal;
import com.abel.domain.AuthRequest;
import com.abel.domain.UserRole;
import com.abel.domain.types.UserType;
import com.abel.repository.AuthRepository;
import com.abel.repository.exception.DuplicateUserException;
import com.abel.repository.exception.InsufficientPermissionException;
import com.abel.repository.exception.InvalidUserCredentialsException;
import com.abel.repository.exception.MultipleUserNameException;
import com.abel.repository.exception.RepositoryException;
import com.abel.repository.helper.QueryUtil;
import com.crypto.CryptoFactory;
import com.crypto.CryptographerType;
import com.crypto.exception.CryptographyException;
import com.crypto.exception.InvalidInputException;

public class AuthRepositoryImpl extends BaseRepositoryImpl implements AuthRepository {

	protected final Log logger = LogFactory.getLog(getClass());
	
	
	@SuppressWarnings("unused")
	private AuthRepository authRepository;
	
	
	@Override
	public AuthPrincipal getAuthPrincipal(AuthRequest authRequest)
			throws RepositoryException, InvalidUserCredentialsException, InsufficientPermissionException {
		
		logger.debug("@AuthRepositoryImpl : getAuthPrincipal : entered");
		
		Transaction tx = null;
		Session session = null;
		
		try {
		
			session = this.getSessionFactory().getCurrentSession();
			tx = session.beginTransaction();
			
			//encrypting password for comparison
			String password = null;
			try {
				
				password = CryptoFactory.getCryptoGrapher(CryptographerType.AES).encrypt(authRequest.getPassword());
			} 
			catch (CryptographyException e) {
				
				logger.error("@AuthRepositoryImpl : addRegistration : CryptographyException" + e);
				e.printStackTrace();
				throw new RepositoryException(e);
				
			} 
			catch (InvalidInputException e) {

				logger.error("@AuthRepositoryImpl : addRegistration : InvalidInputException(Cryptography)" + e);
				e.printStackTrace();
				throw new RepositoryException(e);
				
			}			
			
			@SuppressWarnings("unchecked")
			List<Users> usersList = session.getNamedQuery("getUser").
				setParameter("userName", authRequest.getUserName()).
				setParameter("userPassword", password).list();
			
			if (usersList.size() == 0) {
				throw new InvalidUserCredentialsException("Exception while getting auth info.  User id or password is not valid. " + authRequest.getUserName());
			}
			
			Users users = usersList.get(0);
			
			@SuppressWarnings("unchecked")
			List<UserRole> userRoles = session.getNamedQuery("getUserRoles").
				setParameter("userId", users.getUserId()).list();
			
			if (userRoles.size() == 0) {
				throw new InsufficientPermissionException("Exception while getting auth info.  User does not have the necessary roles set to access the application. " + authRequest.getUserName());
			}
			
			AuthPrincipal authPrincipal = this.convertUsersToDomain(users, false);
			authPrincipal.setRoles(userRoles);
			tx.commit();
			return authPrincipal;
		} 
		catch (RuntimeException e) {
			
			e.printStackTrace();
			
			if (tx != null && tx.isActive()) {
			
				try {
				
					tx.rollback();
				
				}
				catch (HibernateException he) {
				
					logger.debug("@AuthRepositoryImpl : addRegistration : exception while rolling back registration" + he);
					he.printStackTrace();
				
				}
			}
			throw new RepositoryException("Excpetion while adding user=" + authRequest.getUserName(),e);
		}
	}

	@Override
	public int addRegistration(AccountInfo accountInfo)
			throws RepositoryException, DuplicateUserException {
		
		logger.debug("@AuthRepositoryImpl : addRegistration : entered");
		
		Transaction tx = null;
		Session session = null;
		Users usrs = new Users();
		
		try {
			
			session = this.getSessionFactory().getCurrentSession();
			tx = session.beginTransaction();
			//encrypting password before storing it
			String password = null;
			try {
				
				password = CryptoFactory.getCryptoGrapher(CryptographerType.AES).encrypt(accountInfo.getPassword());
			} 
			catch (CryptographyException e) {
				
				logger.error("@AuthRepositoryImpl : addRegistration : CryptographyException" + e);
				e.printStackTrace();
				throw new RepositoryException(e);
				
			} 
			catch (InvalidInputException e) {

				logger.error("@AuthRepositoryImpl : addRegistration : InvalidInputException(Cryptography)" + e);
				e.printStackTrace();
				throw new RepositoryException(e);
				
			}
			//add user information
			usrs.setUserName(accountInfo.getUserName());
			usrs.setUserPassword(password);
			usrs.setUserEmail(accountInfo.getEmail());
			usrs.setUserType(accountInfo.getPersonType().getPersonTypeInd());
			int userId = Integer.parseInt(((Object)session.save(usrs)).toString());
			usrs.setUserId(userId);
			
			//add user role 
			UserRoles userRoles = new UserRoles();
			userRoles.setRoleId(UserType.getUserTypeId(usrs.getUserType()));
			userRoles.setUserId(usrs.getUserId());
			session.save(userRoles);
			tx.commit();
			
			return usrs.getUserId();
		
		} 
		catch (ConstraintViolationException e) {

			logger.error("@AuthRepositoryImpl : addRegistration : exception - more than one user record found for user name = " + usrs.getUserName());
			throw new DuplicateUserException("More than one user record found for user name = " + usrs.getUserName());
			
		}
		catch (RuntimeException e) {

			e.printStackTrace();
			
			if (tx != null && tx.isActive()) {
			
				try {
				
					tx.rollback();
				
				} 
				catch (HibernateException he) {
				
					logger.error("@AuthRepositoryImpl : addRegistration : exception while rolling back registration" + he);
					he.printStackTrace();
				
				}
			}
			throw new RepositoryException("Exception while adding user=" + usrs.getUserName(),e);
		}
	}

	
	
	/* (non-Javadoc)
	 * @see com.abel.repository.AuthRepository#validateAuthCredentials()
	 */
	@Override
	public AuthPrincipal validateAuthCredentials(AuthRequest authRequest) 
		throws RepositoryException, MultipleUserNameException, InvalidUserCredentialsException {
		
		logger.debug("@AuthRepositoryImpl : validateAuthCredentials : entered");

		Session session = null;
		Transaction tx = null;
		
		try {
			session = this.getSessionFactory().getCurrentSession();
			tx = session.beginTransaction();

			Criteria sessionCriteria = session.createCriteria(Users.class);
			if (authRequest.getUserName() != null 
					&& !authRequest.getUserName().equals("")) {
				sessionCriteria.add(Restrictions.ilike("userName",QueryUtil.formatLike(authRequest.getUserName())));
			}
			if (authRequest.getEmail() != null 
					&& !authRequest.getEmail().equals("")) {
				sessionCriteria.add(Restrictions.ilike("userEmail",QueryUtil.formatLike(authRequest.getEmail())));
			}
			if (authRequest.getUserPhone() != null 
					&& !authRequest.getUserPhone().equals("")) {
				sessionCriteria.add(Restrictions.ilike("userPhone",QueryUtil.formatLike(authRequest.getUserPhone())));
			}
			List<Users> list =  sessionCriteria.list();
			
			//verify that only one record is returned
			if (list.size() > 1) {
				throw new MultipleUserNameException("More than one user name exists for the user name " + authRequest.getUserName() + ". Please contact system administrator");
			}
			
			if (list.size() == 1) {
				
				Users users = list.get(0);
				//converting data objects to domain
				AuthPrincipal authPrincipal = this.convertUsersToDomain(users, true);
				return authPrincipal;
				
			}
			else {
				//no record found
				throw new InvalidUserCredentialsException("Credentials passed is invalid.");
			}
			
		}
		catch (Exception e) {
			logger.error("@AuthRepositoryImpl : validateAu{thCredentials : exception - while validate auth credentials" + e);
			e.printStackTrace();
			//had to do it since  there is no specific exception thrown by sessionfactory
			if (e instanceof InvalidUserCredentialsException) {
				throw new InvalidUserCredentialsException(e);
			}
			if (tx != null && tx.isActive()) {
				
				try {
				
					tx.rollback();
				
				} 
				catch (HibernateException he) {
					logger.error("@AuthRepositoryImpl : validateAuthCredentials : exception while rolling back validate auth credentials" + he);
					he.printStackTrace();
				}
			}

			throw new RepositoryException("Exception while searching for records",e);
		}
	}

	
	private AuthPrincipal convertUsersToDomain(Users users, boolean validateRequest) {
		
		AuthPrincipal authPrincipal = new AuthPrincipal();
		authPrincipal.setUserId(users.getUserId());
		
		if (validateRequest) {
			authPrincipal.setCredential(users.getUserPassword());
		}
		authPrincipal.setUserName(users.getUserName());
		authPrincipal.setUserPhone(users.getUserPhone());
		authPrincipal.setUserType(users.getUserType());
		authPrincipal.setEmail(users.getUserEmail());
		return authPrincipal;
	}
	
	@Override
	public void setAuthRepository(AuthRepository authRepository) {
		this.authRepository = authRepository;
		
	}

		
}
